/*
 * SystemSnapshot.java
 * 
 * Created by demory on Mar 18, 2009, 9:38:50 PM
 * 
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fpdev.apps.rtemaster.analysis;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.media.MediaLocator;
import org.fpdev.core.algo.IntervalTree;
import org.fpdev.core.transit.Route;
import org.fpdev.core.transit.SubRoute;
import org.fpdev.apps.rtemaster.RouteMaster;
import org.fpdev.apps.rtemaster.gui.map.Animation;
import org.fpdev.apps.rtemaster.gui.map.MovieRecorder;
import org.fpdev.core.transit.CalendarService;
import org.fpdev.core.transit.TransitPath;
import org.fpdev.util.FPUtil;

/**
 *
 * @author demory
 */
public class Visualizer {

  private RouteMaster ac_;
  private Animation animation_;

  private Map<TransitPath.Type, VehicleRenderOptions> renderOptions_;
  private Map<String, IntervalTree> runTrees_;

  private CalendarService service_;
  private int startTime_, endTime_;

  private int width_, height_, mapWidth_, mapHeight_, graphHeight_, graphWidth_, graphMarginWidth_;
  private int numRows_, numCols_; // for route panel
  private int maxVehicles_;
  private Map<TransitPath.Type, Integer> maxVehByType_;
  private Map<TransitPath.Type, Point2D> lastGraphPoints_;
  private BufferedImage graphImg_;

  public Visualizer(RouteMaster ac) {
    ac_ = ac;

    runTrees_ = new HashMap<String, IntervalTree>(); // maps CalSvc ID to i-tree

    renderOptions_ = new HashMap<TransitPath.Type, VehicleRenderOptions>();

    // specify render defaults:
    renderOptions_.put(TransitPath.Type.BUS, new VehicleRenderOptions(1, 24, 12, 0, 2, TransitPath.Type.BUS.getColor()));
    renderOptions_.put(TransitPath.Type.METRO, new VehicleRenderOptions(3, 12, 12, 2, 2, TransitPath.Type.METRO.getColor()));
    renderOptions_.put(TransitPath.Type.LIGHTRAIL, new VehicleRenderOptions(2, 12, 8, 2, 2, TransitPath.Type.LIGHTRAIL.getColor()));
    renderOptions_.put(TransitPath.Type.REGIONALRAIL, new VehicleRenderOptions(4, 12, 10, 2, 2, TransitPath.Type.REGIONALRAIL.getColor()));
  }

  public Animation getAnimation() {
    return animation_;
  }

  public VehicleRenderOptions getVehicleRenderOptions(TransitPath.Type type) {
    return renderOptions_.get(type);
  }

  public void setVehicleRenderOptions(TransitPath.Type type, VehicleRenderOptions opts) {
    renderOptions_.put(type, opts);
  }

  public void setService(CalendarService service) {
    service_ = service;
    if(!runTrees_.containsKey(service_.getID())) buildIntervalTree(service_);
  }

  private IntervalTree buildIntervalTree(CalendarService service) {

    ac_.msg("building tree");
    Collection<Route> rtes = ac_.getEngine().getRoutes().getCollection(ac_.getActiveScenario());
    Set<VisualizedRun> runs = new HashSet<VisualizedRun>();

    for(Route rte : rtes) {
      for(SubRoute sub : rte.getSubRoutes()) {
        if(!sub.hasTable(service)) continue;
        for(int r = 1; r <= sub.getTable(service).runCount(); r++) {
          runs.add(new VisualizedRun(sub, service, r));
        }
      }
    }

    IntervalTree tree = new IntervalTree(runs);
    runTrees_.put(service.getID(), tree);
    return tree;
  }

  private Set<VisualizedRun> vrResults_;

  public Set<Route> refreshSnapshot(int stime) {
    //ac_.msg("refreshing snapshot");

    Set<Route> activeRoutes = new HashSet<Route>();

    IntervalTree iTree = runTrees_.get(service_.getID());

    vrResults_ = iTree.query(stime);
    //ac_.msg("found "+results.size());
    ac_.getGUI().getMapPanel().getDrawItems().removeFromPrefix("snap_");

    Set<VisualizedVehicle> visibleVVs = new HashSet<VisualizedVehicle>();
    for(VisualizedRun vr : vrResults_) {

      //av_.msg(ri.getSubRoute().getMasterID()+", run "+ri.getRun()+" ("+FPUtil.sTimeToStr(ri.getLower())+" to "+FPUtil.sTimeToStr(ri.getUpper())+")");

      activeRoutes.add(vr.getSubRoute().getRoute());

      VisualizedVehicle vv = null;
      String key = "snap_"+vr.getSubRoute().getMasterID()+"_"+vr.getRun();

      TransitPath.Type type = vr.getSubRoute().getPath().getType();
      VehicleRenderOptions opts = renderOptions_.get(type);

          double locProp = vr.getLocationAtTime(service_, vr.getRun(), stime);
          double pathLenW = vr.getSubRoute().getPath().lengthFt(false);
          //int unitCount = 3, unitLenPx = 12, bufferPx = 2;
          int unitCount = opts.numUnits_, unitLenPx = opts.unitLength_, bufferPx = opts.unitBuffer_;

          int spanPx = (unitLenPx + bufferPx);
          double spanW = ac_.getGUI().getMapPanel().getCanvas().getCC().dxToWorld(spanPx); // span between two adjacent units (center-to-center) in world coords
          double trainSpanW = spanW * (double)(unitCount-1);
          double spanP = spanW / pathLenW;
          double trainSpanP = trainSpanW / pathLenW;

          List<Point2D.Double> ptList = new ArrayList<Point2D.Double>();
          List<Double> thetaList = new ArrayList<Double>();

          double curLocP = locProp + trainSpanP/2;
          for (int i = 0; i < unitCount; i++) {
            if(curLocP >= 0 && curLocP <= 1) {
              Point2D.Double pt[] = vr.getSubRoute().getPath().getPoint(curLocP);
              ptList.add(pt[0]);
              thetaList.add(FPUtil.getTheta(pt[1].x, pt[1].y));
            }
            curLocP -= spanP;
          }
          Point2D.Double[] pts = new Point2D.Double[ptList.size()];
          double[] thetas = new double[ptList.size()];
          for (int i = 0; i < ptList.size(); i++) {
            pts[i] = ptList.get(i);
            thetas[i] = thetaList.get(i);
          }
          vv = new VisualizedVehicle(ac_.getGUI(), vr, pts, thetas, opts);//unitLenPx, 12);

          /*break;
      }*/

      if(vv != null) {
        ac_.getGUI().getMapPanel().getDrawItems().addItem(key, vv, "a");
        if(ac_.getGUI().getMapPanel().getCanvas().getCC().getRange().intersects(vv.getBoundingBox())) //contains(pt[0].x, pt[0].y))
          visibleVVs.add(vv);
      }
    }
    if(!visibleVVs.isEmpty()) {
      ac_.getGUI().getMapPanel().clearPointsOfInterest();
      ac_.getGUI().getMapPanel().setPointsOfInterest(visibleVVs);
    }
    ac_.getGUI().getMapPanel().refresh(false, false, true);

    return activeRoutes;
  }

  public Map<TransitPath.Type, Integer> maxVehiclesInService(double start, double end, double step) {
    IntervalTree iTree = runTrees_.get(service_.getID());

    Map<TransitPath.Type, Integer> maximums = new HashMap<TransitPath.Type, Integer>();
    for(TransitPath.Type type : TransitPath.Type.values())
      maximums.put(type, 0);

    for(double stime = start; stime <= end; stime += step) {
      Set<VisualizedRun> results = iTree.query((int) stime);
      Map<TransitPath.Type, Integer> totals = classifyResultsByType(results);
      for(TransitPath.Type type : TransitPath.Type.values()) {
        int total = totals.get(type);
        if(total > maximums.get(type)) maximums.put(type, total);
      }
    }

    return maximums;
  }

  private Map<TransitPath.Type, Integer> classifyResultsByType(Set<VisualizedRun> results) {

    Map<TransitPath.Type, Integer> totals = new HashMap<TransitPath.Type, Integer>();
    for(TransitPath.Type type : TransitPath.Type.values())
      totals.put(type, 0);

    for(VisualizedRun vr : results) {
      TransitPath.Type type = vr.getSubRoute().getRoute().getRepresentativeType();
      totals.put(type, totals.get(type)+1);
    }

    return totals;
  }

  public void recordMovie(double start, double end, double speed) {
    //int width = ac_.getGUI().getMapPanel().getWidth(), height = ac_.getGUI().getMapPanel().getHeight();
    startTime_ = (int) start;
    endTime_ = (int) end;

    mapWidth_ = ac_.getGUI().getMapPanel().getWidth();
    mapHeight_ = ac_.getGUI().getMapPanel().getHeight();

    int numRoutes = ac_.getEngine().getRoutes().routeCount(ac_.getActiveScenario(), true);
    numRows_ = (mapHeight_ - 25) / 14;
    numCols_ = numRoutes/numRows_ + (numRoutes % numRows_ == 0 ? 0 : 1);
    System.out.println("cols = "+numCols_+" rows = "+numRows_);

    graphHeight_ = 100;

    width_ = mapWidth_ + numCols_*50 + 20;
    height_ = mapHeight_ + graphHeight_ + 20;

    graphWidth_ = width_ - 20;
    graphMarginWidth_ = 25;


    double fps = 24;
    double movieLenS = (end-start) / speed;
    double frameCount = fps * movieLenS;
    double step = (end-start) / frameCount;

    maxVehByType_ = maxVehiclesInService(start, end, step);
    maxVehicles_ = 0;
    for(TransitPath.Type type : TransitPath.Type.values())
      maxVehicles_ = Math.max(maxVehByType_.get(type), maxVehicles_);
      //System.out.println("max "+type.getName()+"="+maxVehicles_.get(type));
    initGraphImage();
    
    // Generate the output media locators.
    MediaLocator oml;

    String outputURL = "file:/home/demory/5p/test.mov";
    if ((oml = new MediaLocator(outputURL)) == null) {
	    System.err.println("Cannot build media locator from: " + outputURL);
	    return;
    }

    MovieRecorder recorder = new MovieRecorder();

    recorder.run(width_, height_, (int) fps, oml, (int) start, (int) step, (int) frameCount, this);

  }

  public void getFrame(int stime) {
    try {
      Set<Route> activeRoutes = refreshSnapshot(stime);
      //int width = ac_.getGUI().getMapPanel().getWidth();
      //int height = ac_.getGUI().getMapPanel().getHeight();
      BufferedImage img = new BufferedImage(width_, height_, BufferedImage.TYPE_INT_RGB);
      Graphics2D g2d = img.createGraphics();

      RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
      g2d.setRenderingHints(renderHints);

      g2d.setColor(Color.white);
      g2d.fillRect(0, 0, width_, height_);

      ac_.getGUI().getMapPanel().paintAll(g2d);

      g2d.setColor(Color.black);
      g2d.setFont(new Font("Arial", Font.BOLD, 32));
      g2d.drawString(FPUtil.sTimeToStr(stime), 10, 40);

      paintRoutesPanel(g2d, activeRoutes);
      paintGraphPanel(g2d, stime);

      ImageIO.write(img, "jpeg", new File("/home/demory/5p/temp/foo.jpg"));
    } catch (IOException ex) {
      Logger.getLogger(Visualizer.class.getName()).log(Level.SEVERE, null, ex);
    }
    //return img;
  }

  private void paintRoutesPanel(Graphics2D g2d, Set<Route> activeRoutes) {
    int i = 0;
    g2d.setColor(Color.gray);
    g2d.fillRect(mapWidth_+20, 5, numCols_*50-10, 20);
    g2d.setColor(Color.white);
    g2d.setFont(new Font("Arial", Font.BOLD, 16));
    g2d.drawString("ACTIVE ROUTES", mapWidth_+24, 22);

    g2d.setFont(new Font("Arial", Font.PLAIN, 11));
    for(Route rte : ac_.getEngine().getRoutes().getCollection(ac_.getActiveScenario(), true)) {
      if(activeRoutes.contains(rte)) g2d.setColor(rte.getRepresentativeType().getColor());
      else g2d.setColor(Color.lightGray);

      int row = i % numRows_;
      int col = i / numRows_;

      g2d.drawString(rte.getCombinedID(), mapWidth_+20+col*50, 40+row*14);
      i++;
    }
  }

  private void initGraphImage() {
    graphImg_ = new BufferedImage(graphWidth_, graphHeight_, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = (Graphics2D) graphImg_.getGraphics();
    g2d.setColor(new Color(220, 220, 220));
    g2d.fillRect(0, 0, graphWidth_, graphHeight_);

    g2d.setColor(Color.LIGHT_GRAY);
    g2d.fillRect(0, 0, graphMarginWidth_, graphHeight_);

    g2d.setColor(Color.BLACK);
    g2d.setFont(new Font("Arial", Font.PLAIN, 10));
    g2d.drawString(""+maxVehicles_, 3, 11);
    g2d.drawString("0", 3, graphHeight_-2);

    lastGraphPoints_ = new HashMap<TransitPath.Type, Point2D>();
    for(TransitPath.Type type : TransitPath.Type.values()) {
      lastGraphPoints_.put(type, new Point2D.Double(graphMarginWidth_, graphHeight_));
    }

  }

  private void paintGraphPanel(Graphics2D mainG2D, int stime) {
    Map<TransitPath.Type, Integer> values = classifyResultsByType(vrResults_);

    Graphics2D graphG2D = (Graphics2D) graphImg_.getGraphics();

    
    for(TransitPath.Type type : TransitPath.Type.values()) {
      if(maxVehByType_.get(type) == 0) continue;
      double yFact = (double) values.get(type) / (double) maxVehicles_;
      double xFact = (double) (stime - startTime_) / (double) (endTime_ - startTime_);

      graphG2D.setColor(type.getColor());
      int x = (int) (graphMarginWidth_ + xFact * (graphWidth_-graphMarginWidth_));
      int y = (int) (graphHeight_ - yFact * graphHeight_);
      //graphG2D.fillRect(x, y, 2, 2);
      Point2D last = lastGraphPoints_.get(type);
      graphG2D.drawLine((int) last.getX(), (int) last.getY(), x, y);
      lastGraphPoints_.put(type, new Point2D.Double(x, y));
    }

    mainG2D.drawImage(graphImg_, 10, mapHeight_+10, null);

    mainG2D.setFont(new Font("Arial", Font.PLAIN, 11));

    for(TransitPath.Type type : TransitPath.Type.values()) {
      if(maxVehByType_.get(type) == 0) continue;
      double yFact = (double) values.get(type) / (double) maxVehicles_;
      double xFact = (double) (stime - startTime_) / (double) (endTime_ - startTime_);

      int x = (int) (graphMarginWidth_ + xFact * (graphWidth_-graphMarginWidth_));
      int y = (int) (graphHeight_ - yFact * graphHeight_);
      mainG2D.setColor(type.getColor());
      mainG2D.drawString(""+values.get(type), x+12, mapHeight_+y+15);
    }
  }


  // ANIMATION METHODS

  public void initAnimation(int start, int end, int speed) {
    if (animation_ != null) {
      ac_.getGUI().getMapPanel().setIsAnimating(false);
      animation_.pause();
    }
    animation_ = new Animation(start, end, 10, speed, ac_.getGUI(), this);
  }
}
